/*
 *  native.cpp
 *  joosVM
 *
 *  Created by Smári Waage on 7.9.2009.
 *  Copyright 2009 smari.org. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. *
 */

//#include "native.h"
#include "interpreter.h"
#include <dirent.h>
#include <math.h>
#include <sys/time.h>


bool first_time = true;

inline void printlnString_f(String* st){
	//TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	//String* str = (String*)va->getValue();
	printf("%s\n",st->str());
}
inline void printlnInt_f(int n){
	printf("%i\n",n);
}
inline void printString_f(String* st){
	//TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	//String* str = (String*)va->getValue();
	printf("%s",st->str());
}
inline void printInt_f(int n){
	printf("%i",n);
}

#if TRACE_JIT 
MAKE_CALLINFO(printlnString_f,ARGSIZE_NONE,1,ABI_STDCALL);
MAKE_CALLINFO(printlnInt_f,ARGSIZE_NONE,1,ABI_STDCALL);
MAKE_CALLINFO(printString_f,ARGSIZE_NONE,1,ABI_STDCALL);
MAKE_CALLINFO(printInt_f,ARGSIZE_NONE,1,ABI_STDCALL);
#endif



 native_func NativeMethods::linkMethod(JMethod* nMethod){
	//Todo: store the function pointer in the method.
	//Special case to handle the lack of standard libs.
	native_func r;
	if(!strcmp(nMethod->getName()->str(), "println")){
		
		const char* sig = ((String*)nMethod->getReturnType())->str();
		
		if(!strcmp(sig, "(I)V") ){
			r.func = int_println;
	
		}else if(!strcmp(sig, "(Ljava/lang/String;)V")){
			r.func = str_println;
		}else if(!strcmp(sig, "(D)V") ){
			r.func = dbl_println;
		}else{
			r.func = NULL;
		}
		
		
	}else if(!strcmp(nMethod->getName()->str(), "print")){
		
		const char* sig = ((String*)nMethod->getReturnType())->str();
		if(!strcmp(sig, "(I)V") ){
			r.func = int_print;
		}else if(!strcmp(sig, "(Ljava/lang/String;)V")){
			r.func = str_print;
		}else if(!strcmp(sig, "(D)V") ){
			r.func = dbl_print;
		}else{
			r.func = NULL;
		}
		
		
	}else if(!strcmp(nMethod->getName()->str(), "currentTimeMillis")){
		r.func = currentTimeMillis;
	}else if(!strcmp(nMethod->getName()->str(), "list")){
		r.func = list;
	}else if(!strcmp(nMethod->getName()->str(), "substring")){
		r.func = substring;
	}else if(!strcmp(nMethod->getName()->str(), "length")){
		r.func = length;
	}else if(!strcmp(nMethod->getName()->str(), "equals")){
		r.func = equals;
	}else if(!strcmp(nMethod->getName()->str(), "concat")){
		r.func = concat;
	}else if(!strcmp(nMethod->getName()->str(), "forName")){
		r.func = forName;
	}else if(!strcmp(nMethod->getName()->str(), "getMethod")){
		r.func = getMethod;
	}else if(!strcmp(nMethod->getName()->str(), "invoke")){
		r.func = invoke;
	}else if(!strcmp(nMethod->getName()->str(), "parseInt")){
		r.func = parseInt;
	}else if(!strcmp(nMethod->getName()->str(), "getRandomInt")){
		r.func = getRandomInt;	
	}else{
		r.func = NULL;
	}
	
	 return r;
}


void NativeMethods::callNativeMethod(JMethod* nMethod, TaggedArray* locals, Stack* s){
	
	//Todo: store the function pointer in the method.
	//Special case to handle the lack of standard libs.
		
	if(!strcmp(nMethod->getName()->str(), "println")){
		
		const char* sig = ((String*)nMethod->getReturnType())->str();
		
		if(!strcmp(sig, "(I)V") ){
			int_println(locals, s);
		}else if(!strcmp(sig, "(Ljava/lang/String;)V")){
			str_println(locals, s);
		}else if(!strcmp(sig, "(D)V") ){
			dbl_println(locals, s);
		}else{
			printf("Bad native call!!");exit(1);
		}
		
		
	}else if(!strcmp(nMethod->getName()->str(), "print")){
		
		const char* sig = ((String*)nMethod->getReturnType())->str();
		if(!strcmp(sig, "(I)V") ){
			int_print(locals, s);
		}else if(!strcmp(sig, "(Ljava/lang/String;)V")){
			str_print(locals,s);
		}else if(!strcmp(sig, "(D)V") ){
			dbl_print(locals,s);
		}else{
			printf("Bad native call!!");exit(1);
		}

		
	}else if(!strcmp(nMethod->getName()->str(), "currentTimeMillis")){
		currentTimeMillis(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "list")){
		list(locals,s);
	}else if(!strcmp(nMethod->getName()->str(), "substring")){
		substring(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "length")){
		length(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "equals")){
		equals(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "concat")){
		concat(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "forName")){
		forName(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "getMethod")){
		getMethod(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "invoke")){
		invoke(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "parseInt")){
		parseInt(locals, s);
	}else if(!strcmp(nMethod->getName()->str(), "getRandomInt")){
		getRandomInt(locals, s);	
	}else{
		printf("Bad native call!!");exit(1);
	}
	
	
	
	
	
	
}

//PrintStream
void NativeMethods::str_println(TaggedArray* locals, Stack* s){
	Instance* inst = (Instance*)locals->get(1);
	TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	printlnString_f(str);
	TRACE_REC(
			  LIns* args[] = {rec->insertPointer(str)};//disgard the boxed pointer just get the c-string directly.
			  rec->pop();
			  rec->pop(); //invoke virtual;
			  rec->lir->insCall(&printlnString_f_ci,args);
			  )
	
}
void NativeMethods::str_print(TaggedArray* locals, Stack* s){
	Instance* inst = (Instance*)locals->get(1);
	TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	printString_f(str);
	TRACE_REC(
			  LIns* args[] = {rec->insertPointer(str)};
			  rec->pop();
			  rec->pop(); //invoke virtual;
			  rec->lir->insCall(&printString_f_ci,args);
			  )
}
void NativeMethods::int_println(TaggedArray* locals, Stack* s){
	printlnInt_f((int)locals->get(1));
	TRACE_REC(
			  LIns* args[] = {rec->pop()};
			  rec->pop(); //invoke virtual;
			  rec->lir->insCall(&printlnInt_f_ci,args);
			  )
}

void NativeMethods::int_print(TaggedArray* locals, Stack* s){
	printInt_f((int)locals->get(1));
	TRACE_REC(
			  LIns* args[] = {rec->pop()};
			  rec->pop(); //invoke virtual;
			  rec->lir->insCall(&printInt_f_ci,args);
			  )
}

void NativeMethods::dbl_println(TaggedArray* locals, Stack* s){
	u8_t d;
	d.low = (u4)locals->get(1);
	d.high = (u4)locals->get(2);
	printf("%f\n",d._double);
	TRACE_REC(BLACKLIST_TRACE)
}
void NativeMethods::dbl_print(TaggedArray* locals, Stack* s){
	u8_t d;
	d.low = (u4)locals->get(1);
	d.high = (u4)locals->get(2);
	printf("%f",d._double);
	TRACE_REC(BLACKLIST_TRACE)
}


//static void _pow(TaggedArray* locals, Stack* s);
//java.lang.System
void NativeMethods::currentTimeMillis(TaggedArray* locals, Stack* s){
	timeval t;
	gettimeofday(&t, 0);
	/* convert struct timeval to ms(milliseconds) */
	s->pushLong(((t.tv_sec * 1000) + (t.tv_usec / 1000)));
	TRACE_REC(BLACKLIST_TRACE)
}

//java.io.list
void NativeMethods::list(TaggedArray* locals, Stack* s){
	//not nice
	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* fi = (TaggedValue*)inst->getValues()->lookup(String::charToString("path"));
	
	Instance* inst2 = (Instance*)fi->getValue();
	TaggedValue* va = (TaggedValue*)inst2->getValues()->lookup(String::charToString("c_str"));
	
	String* spath = (String*)va->getValue();
	
	char* filepath = (char *)calloc(strlen(spath->str()) + strlen(ClassLoader::classpath) + 1, sizeof(char));
	strcat(filepath, ClassLoader::classpath);
	strcat(filepath, spath->str());
	
	
	DIR *dp;
	struct dirent *dirp;
	if((dp = opendir(filepath)) == NULL) {
		printf("error reading directory");
	}
	int count = 0;	
	while ((dirp = readdir(dp)) != NULL) {
		if(dirp->d_name[0] != '.'){
			count++;
		}
	}
	rewinddir(dp);
	StaticArray* st= StaticArray::Initalize(count);
	//st->setArrayType((HeapObject*) Smi::fromInt(IType_Array));
	s->pushRef(st);
	count = 0;
	JType* tString = ClassLoader::findType(String::charToString("java/lang/String"));
	JField* fd = tString->getField(String::charToString("c_str"));
	while ((dirp = readdir(dp)) != NULL) {
		if(dirp->d_name[0] != '.'){
			Instance* bla = Instance::Initialize(tString);
			bla->getValues()->set(fd->getName(),TaggedValue::Initalize(String::charToString(dirp->d_name), true));
			st->set(count++,bla);
			
			//printf("%s\n",dirp->d_name);
		}
	}
	
	closedir(dp);
	free(filepath);
	
	TRACE_REC(BLACKLIST_TRACE)
}

//java.lang.String
void NativeMethods::substring(TaggedArray* locals, Stack* s){
	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	int start = (int)locals->get(1);
	int end;
	if(locals->array_length() >2){
		end = (int)locals->get(2);
	}else{
		end = str->string_length();
	}
	JType* tString = ClassLoader::findType(String::charToString("java/lang/String"));
	JField* fd = tString->getField(String::charToString("c_str"));
	std::string sub(str->str());
	Instance* bla = Instance::Initialize(tString);
	bla->getValues()->set(fd->getName(),TaggedValue::Initalize(String::charToString(sub.substr(start, end).c_str()), true));
	s->pushRef(bla);
	//TRACE_REC(lassert(false,"unimplemented record");)
TRACE_REC(BLACKLIST_TRACE)
}
void NativeMethods::length(TaggedArray* locals, Stack* s){
	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	s->pushPrim((Pointer*)str->array_length()-1);
	TRACE_REC(BLACKLIST_TRACE)
}
void NativeMethods::equals(TaggedArray* locals, Stack* s){
	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	
	Instance* inst2 = (Instance*)locals->get(1);
	TaggedValue* va2 = (TaggedValue*)inst2->getValues()->lookup(String::charToString("c_str"));
	String* str2 = (String*)va2->getValue();
	
	//ddout("equals %s == %s\n",str->str(),str2->str());
	
	if(!strcmp(str->str(), str2->str()))
		s->pushPrim((Pointer*)1);
	else {
		s->pushPrim((Pointer*)0);
	}
	
	//TRACE_REC(lassert(false,"unimplemented record");)
	TRACE_REC(BLACKLIST_TRACE)
}
void NativeMethods::concat(TaggedArray* locals, Stack* s){
	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	
	Instance* inst2 = (Instance*)locals->get(1);
	TaggedValue* va2 = (TaggedValue*)inst2->getValues()->lookup(String::charToString("c_str"));
	String* str2 = (String*)va2->getValue();
	
	std::string st1(str->str());
	std::string st2(str2->str());
	
	st1.append(st2);
	
	JType* tString = ClassLoader::findType(String::charToString("java/lang/String"));
	JField* fd = tString->getField(String::charToString("c_str"));
	std::string sub(str->str());
	Instance* bla = Instance::Initialize(tString);
	bla->getValues()->set(fd->getName(),
						  TaggedValue::Initalize(
												 String::charToString(st1.c_str()), true));
	s->pushRef(bla);
	//TRACE_REC(lassert(false,"unimplemented record");)
	TRACE_REC(BLACKLIST_TRACE)	
}

//java.lang.Class
void NativeMethods::forName(TaggedArray* locals, Stack* s){
	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	
	std::string st1(str->str());
	st1.replace(st1.find("."),1,"/");
	
	dout(debug_interpreter,"Class.forName(%s)",st1.c_str());
	
	JType* tp = ClassLoader::findType(String::charToString(st1.c_str()));
	if(tp == NULL){printf("java/lang/ClassNotFoundException");exit(1);}
	
	JType* klass = ClassLoader::findType(String::charToString("java/lang/Class"));
	Instance* bla = Instance::Initialize(klass);
	bla->getValues()->set(String::charToString("type"), TaggedValue::Initalize(tp, true));
	s->pushRef(bla);
	//TRACE_REC(lassert(false,"unimplemented record");)
	TRACE_REC(BLACKLIST_TRACE)	
}
void NativeMethods::getMethod(TaggedArray* locals, Stack* s){

	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* ttype = (TaggedValue*)inst->getValues()->lookup(String::charToString("type"));
	JType* type = (JType*)ttype->getValue();
	
	Instance* inst2 = (Instance*)locals->get(1);
	TaggedValue* va = (TaggedValue*)inst2->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	std::string st1(str->str());
	st1.append("()I");
	
	JMethod* met = (JMethod*)type->getMethods()->lookup(String::charToString(st1.c_str()));
	if(met == NULL){printf("java/lang/NoSuchMethodException");exit(1);}
	
	JType* metKlass = ClassLoader::findType(String::charToString("java/lang/reflect/Method"));
	Instance* bla = Instance::Initialize(metKlass);
	bla->getValues()->set(String::charToString("method"), TaggedValue::Initalize(met, true));
	s->pushRef(bla);
	//TRACE_REC(lassert(false,"unimplemented record");)
	TRACE_REC(BLACKLIST_TRACE)	
}

//java.lang.reflect.Method
void NativeMethods::invoke(TaggedArray* locals, Stack* s){

	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* ttype = (TaggedValue*)inst->getValues()->lookup(String::charToString("method"));
	JMethod* met = (JMethod*)ttype->getValue();
	
	JType* intType = ClassLoader::findType(String::charToString("java/lang/Integer"));
	JMethod* boxMet = (JMethod*)intType->getMethods()->lookup(String::charToString("boxNextInt()Ljava/lang/Integer;"));
	if(boxMet == NULL){printf("java/lang/NoSuchMethodException");exit(1);}
	//This is a hack to get a method in to a right position 
	s->pushOnFrame(boxMet, TaggedArray::Initalize(0));
	s->getnextbyte();//I know its new, just to place the stack in the right position.
	Bytecodes::_new(s);
	s->getnextbyte();//I know its dup
	Bytecodes::dup(s);
	s->getnextbyte();//ignore const 1;
	//and push on the result from the call to test ()I...
	s->pushOnFrame(met, TaggedArray::Initalize(met->getMaxLocals()));
	//then when that returns the value gets boxed.
	//not nice but quick and easy.
	
	//TODO: Make nicer, and general
	//TRACE_REC(lassert(false,"unimplemented record");)
	TRACE_REC(BLACKLIST_TRACE)
	
}

//java.lang.Integer
void NativeMethods::parseInt(TaggedArray* locals, Stack* s){
	Instance* inst = (Instance*)locals->get(0);
	TaggedValue* va = (TaggedValue*)inst->getValues()->lookup(String::charToString("c_str"));
	String* str = (String*)va->getValue();
	s->pushPrim((Pointer*)atoi(str->str()));
	//TRACE_REC(lassert(false,"unimplemented record");)
	TRACE_REC(BLACKLIST_TRACE)
}


//Special 
void NativeMethods::getRandomInt(TaggedArray* locals, Stack* s){
	if(first_time){
		srand( (unsigned int)time( NULL ) );
		first_time = false;
	}
	int r = rand();
	s->pushPrim((Pointer*)r);
	TRACE_REC(BLACKLIST_TRACE)
}


